/* The routines in this file provide menu-related functions under the
   Microsoft Windows environment on an IBM-PC or compatible computer.

   This module also contains the code for the About and Modes dialog
   boxes.

   Must be compiled with Borland C++ 2.0 or MSC 6.0 or later versions.

   It should not be compiled if the WINDOW_MSWIN symbol is not set */

#include    "estruct.h"
#include    "elang.h"
#include    <stdio.h>
#include    "eproto.h"
#include    "edef.h"

#include    "mswin.h"
#include    "mswmenu.h"
#include    "mswhelp.h"

#define MAXMENUTITLE 50

/* codes for the MenuType parameter from AddMenuEntry () */
#define MT_DUMMY    1
#define MT_MENUBAR  2

typedef struct   {
    WORD    m_word;
    ETYPE EPOINTER m_ptr;
} MENUTAB;

#define MB_FNC  0x4000
#define MB_BUF  0x8000  /* static IDs should not look like they carry
			   this flag */

#define MAXDYNMENU  256

static MENUTAB MenuDynBind [MAXDYNMENU] = {0, NULL};

static MENUTAB MenuStaticBind [] = {
    {IDM_NULLPROC,      nullproc},
    {IDM_FILEFIND,      filefind},
    {IDM_VIEWFILE,      viewfile},
    {IDM_INSFILE,	insfile},
    {IDM_FILEREAD,	fileread},
    {IDM_FILENAME,	filename},
    {IDM_FILESAVE,	filesave},
    {IDM_FILEWRITE,	filewrite},
    {IDM_FILEAPP,	fileapp},
    {IDM_SETEKEY,	setekey},
    {IDM_NEXTBUFFER,	nextbuffer},
    {IDM_USEBUFFER,	usebuffer},
    {IDM_UNMARK,	unmark},
    {IDM_NAMEBUFFER,	namebuffer},
    {IDM_KILLBUFFER,	killbuffer},
    {IDM_NARROW,	narrow},
    {IDM_WIDEN,		widen},
    {IDM_LISTBUFFERS,	listbuffers},
    {IDM_SPLITWIND,	splitwind},
    {IDM_DELWIND,	delwind},
    {IDM_ONLYWIND,	onlywind},
    {IDM_NEXTWIND,	nextwind},
    {IDM_PREVWIND,	prevwind},
    {IDM_MVUPWIND,	mvupwind},
    {IDM_MVDNWIND,	mvdnwind},
    {IDM_NEXTUP,	nextup},
    {IDM_NEXTDOWN,	nextdown},
    {IDM_ENLARGEWIND,	enlargewind},
    {IDM_SHRINKWIND,	shrinkwind},
    {IDM_RESIZE,	resize},
    {IDM_QUICKEXIT,	quickexit},
    {IDM_QUIT,		quit},
        /* here just to compute the key binding: the actual IDM_QUIT
	   selection is processed as a special */
    {IDM_CUTREGION,	cutregion},
    {IDM_CLIPREGION,	clipregion},
    {IDM_INSERTCLIP,	insertclip},
    {IDM_SETMARK,	setmark},
    {IDM_REMMARK,	remmark},
    {IDM_SWAPMARK,	swapmark},
    {IDM_YANK,		yank},
    {IDM_KILLREGION,	killregion},
    {IDM_COPYREGION,	copyregion},
    {IDM_UPPERREGION,	upperregion},
    {IDM_LOWERREGION,	lowerregion},
    {IDM_ENTAB,		entab},
    {IDM_DETAB,		detab},
    {IDM_TRIM,		trim},
    {IDM_INDENT_REGION,	indent_region},
    {IDM_UNDENT_REGION,	undent_region},
    {IDM_WORDCOUNT,	wordcount},
    {IDM_FILLPARA,	fillpara},
    {IDM_KILLPARA,	killpara},
    {IDM_KILLTEXT,	killtext},
    {IDM_OPENLINE,	openline},
    {IDM_DELFWORD,	delfword},
    {IDM_DELBWORD,	delbword},
    {IDM_CAPWORD,	capword},
    {IDM_LOWERWORD,	lowerword},
    {IDM_UPPERWORD,	upperword},
    {IDM_DEBLANK,	deblank},
    {IDM_TWIDDLE,	twiddle},
    {IDM_TAB,		tab},
    {IDM_QUOTE,		quote},
    {IDM_FORWSEARCH,	forwsearch},
    {IDM_BACKSEARCH,	backsearch},
    {IDM_FORWHUNT,	forwhunt},
    {IDM_BACKHUNT,	backhunt},
    {IDM_FISEARCH,	fisearch},
    {IDM_RISEARCH,	risearch},
    {IDM_SREPLACE,	sreplace},
    {IDM_QREPLACE,	qreplace},
    {IDM_GOTOMARK,	gotomark},
    {IDM_GOTOLINE,	gotoline},
    {IDM_GETFENCE,	getfence},
    {IDM_GOTOBOB,	gotobob},
    {IDM_GOTOEOB,	gotoeob},
    {IDM_FORWPAGE,	forwpage},
    {IDM_BACKPAGE,	backpage},
    {IDM_GOTOEOP,	gotoeop},
    {IDM_GOTOBOP,	gotobop},
    {IDM_FORWLINE,	forwline},
    {IDM_BACKLINE,	backline},
    {IDM_GOTOBOL,	gotobol},
    {IDM_GOTOEOL,	gotoeol},
    {IDM_FORWWORD,	forwword},
    {IDM_BACKWORD,	backword},
    {IDM_ENDWORD,	endword},
    {IDM_EXECPRG,	execprg},
    {IDM_SPAWNCLI,	spawncli},
    {IDM_SPAWN,		spawn},
    {IDM_PIPECMD,	pipecmd},
    {IDM_FILTER,	filter},
    {IDM_CTLXE,		ctlxe},
    {IDM_CTLXLP,	ctlxlp},
    {IDM_CTLXRP,	ctlxrp},
    {IDM_NAMEDCMD,	namedcmd},
    {IDM_EXECCMD,	execcmd},
    {IDM_EXECPROC,	execproc},
    {IDM_EXECBUF,	execbuf},
    {IDM_EXECFILE,	execfile},
    {IDM_BINDTOKEY,	bindtokey},
    {IDM_MACROTOKEY,	macrotokey},
    {IDM_BINDTOMENU,	bindtomenu},
    {IDM_MACROTOMENU,	macrotomenu},
    {IDM_UNBINDKEY,	unbindkey},
    {IDM_UNBINDMENU,    unbindmenu},
    {IDM_DESKEY,	deskey},
    {IDM_SETVAR,	setvar},
    {IDM_DISPVAR,	dispvar},
    {IDM_DESVARS,	desvars},
    {IDM_SHOWCPOS,	showcpos},
    {IDM_CTRLG,		ctrlg},
    {IDM_FIND_SCREEN,	find_screen},
    {IDM_RENAME_SCREEN,	rename_screen},
    {IDM_NEWSIZE,	newsize},
    {IDM_NEWWIDTH,	newwidth},
    {IDM_DESBIND,	desbind},
    {IDM_DESFUNC,	desfunc},
    {IDM_APRO,		apro},
    {0,                 NULL}
};

#define MAXMENUPARENT   7

typedef struct {    /* current menu description */
    int     cm_pos;     /* entry position */
    int     cm_x;       /* index of the last parent handle, -1 if entry
			   located in main menu bar */
    HMENU   cm_parent[MAXMENUPARENT];
                        /* parent handle, main popup menu at index 0 */
} CURMENU;

static CURMENU  CurrentMenu = {-1, -1, {NULL}};

static unsigned int ctrlx_key = 0;
static unsigned int meta_key = 0;       /* for GetKeyText */

/* execmenu:    execute a function bound to a menu */
/* ========                                        */

/* This function is called by the edit loop in main.c, when a MENU
   extended character is detected. */

int PASCAL execmenu (int f, int n)
/* f, n: arguments to target function */
{
    register UINT    ID;
    register MENUTAB *MTp;

    ID = (xpos << 8) + ypos;    /* get_key sees MENU sequences as MOUS */
    if (ID >= IDM_DYNAMIC) {
        MTp = &MenuDynBind[ID - IDM_DYNAMIC];
        if (MTp->m_word & MB_FNC) return (*(MTp->m_ptr.fp)) (f, n);
        else if (MTp->m_word & MB_BUF) {
            BOOL    status;

            if (f == FALSE) n = 1;
            while (n--) {
                status = dobuf (MTp->m_ptr.buf);
                if (status != TRUE) return status;
            }
        }
        else return FAILD;
        return TRUE;
    }
    else for (MTp = &MenuStaticBind[0]; MTp->m_word != 0; MTp++) {
        if (ID == MTp->m_word) return (*(MTp->m_ptr.fp)) (f, n);
    }
    return FAILD;      /* not found !!! */
} /* execmenu */

/* GenerateMenuSeq: send a menu sequence into the input stream */
/* ===============                                             */

void FAR PASCAL GenerateMenuSeq (UINT ID)
{
    if (!in_room (5)) return;
    in_put (0);             /* escape indicator */
    in_put (MENU >> 8);     /* MENU prefix */
    in_put (ID >> 8);       /* menu ID high byte (--> xpos) */
    in_put (ID & 0xFF);     /* menu ID low byte (--> ypos) */
    in_put ('m');           /* dummy event */
} /* GenerateMenuSeq */

/* AboutDlgProc:  About box dialog function */
/* ============                             */
int EXPORT FAR PASCAL  AboutDlgProc (HWND hDlg, UINT wMsg, UINT wParam,
                                     LONG lParam)
{
    char    s [50];
    static RECT FullBox;
    
    switch (wMsg) {
        
    case WM_INITDIALOG:
        strcpy (s, PROGNAME);
        strcat (s, " ");
        strcat (s, VERSION);
        SetDlgItemText (hDlg, ID_PROGVER, s);
        {
            RECT    Bar;

	    GetWindowRect (hDlg, &FullBox);
            GetWindowRect (GetDlgItem (hDlg, ID_ABOUTBAR), &Bar);
            MoveWindow (hDlg, FullBox.left, FullBox.top,
                        Bar.left + GetSystemMetrics (SM_CXDLGFRAME)
                                 - FullBox.left,
                        FullBox.bottom - FullBox.top,
                        FALSE);     /* shrink dialog box, do not repaint */
        }
	return TRUE;
	
    case WM_COMMAND:
	if (NOTIFICATION_CODE != BN_CLICKED) break;
	if (LOWORD(wParam) == 1) {
	    EndDialog (hDlg, 0);
	    return TRUE;
	}
	else if (LOWORD(wParam) == ID_MOREABOUT) {
	    EnableWindow ((HWND)lParam, FALSE);   /* disable "More >>" */
	    SetFocus (GetDlgItem (hDlg, 1));         /* make "OK" default */
	    MoveWindow (hDlg, FullBox.left, FullBox.top,
	                FullBox.right - FullBox.left,
                        FullBox.bottom - FullBox.top,
                        TRUE);      /* enlarge dialog box and repaint */
	    return TRUE;
	}
	break;
    default:
	return FALSE;
    }
    return FALSE;
} /* AboutDlgProc */

/* SetCheck:    puts a check mark in a check box */
/* ========                                      */

void  PASCAL    SetCheck (HWND hDlg, int BoxID)
{
    SendMessage (GetDlgItem (hDlg, BoxID), BM_SETCHECK, 1, 0L);
} /* SetCheck */

/* GetCheck:    TRUE is the check box is checked */
/* ========                                      */

BOOL  PASCAL    GetCheck (HWND hDlg, int BoxID)
{
    return (SendMessage (GetDlgItem (hDlg, BoxID), BM_GETCHECK, 0, 0L) != 0);
} /* GetCheck */

/* ModeDlgProc: Modes dialog box function */
/* ===========                            */

/* must be invoked through DialogBoxParam, with LOWORD(dwInitParam) set
   to TRUE for global modes and FALSE for current buffer modes */

int EXPORT FAR PASCAL  ModeDlgProc (HWND hDlg, UINT wMsg, UINT wParam,
                                    LONG lParam)
{
    char    s[40+NBUFN];
    static int *modep;      /* mode flags pointer */
    
    switch (wMsg) {
        
    case WM_INITDIALOG:
        if (LOWORD(lParam)) {
	    strcpy (s, TEXT331);    /* "Global modes" */
	    modep = &gmode;
	}
        else {
	    strcpy (s, TEXT332);    /* "Modes for buffer: " */
	    strcat (s, curbp->b_bname);
	    modep = &(curbp->b_mode);
	}
        SetWindowText (hDlg, s);
        if (*modep & MDWRAP) SetCheck (hDlg, ID_WRAP);
	if (*modep & MDCMOD) SetCheck (hDlg, ID_CMODE);
        if (*modep & MDEXACT) SetCheck (hDlg, ID_EXACT);
        if (*modep & MDVIEW) SetCheck (hDlg, ID_VIEW);
        if (*modep & MDOVER) SetCheck (hDlg, ID_OVER);
        if (*modep & MDREPL) SetCheck (hDlg, ID_REP);
        if (*modep & MDMAGIC) SetCheck (hDlg, ID_MAGIC);
        if (*modep & MDCRYPT) SetCheck (hDlg, ID_CRYPT);
        if (*modep & MDASAVE) SetCheck (hDlg, ID_ASAVE);
	return TRUE;
	
    case WM_COMMAND:
	if (NOTIFICATION_CODE != BN_CLICKED) break;
	switch (LOWORD(wParam)) {
	    
	case 1:		/* OK */
	    *modep = 0;
	    if (GetCheck (hDlg, ID_WRAP)) *modep |= MDWRAP;
	    if (GetCheck (hDlg, ID_CMODE)) *modep |= MDCMOD;
	    if (GetCheck (hDlg, ID_EXACT)) *modep |= MDEXACT;
	    if (GetCheck (hDlg, ID_VIEW)) *modep |= MDVIEW;
	    if (GetCheck (hDlg, ID_OVER)) *modep |= MDOVER;
	    if (GetCheck (hDlg, ID_REP)) *modep |= MDREPL;
	    if (GetCheck (hDlg, ID_MAGIC)) *modep |= MDMAGIC;
	    if (GetCheck (hDlg, ID_CRYPT)) *modep |= MDCRYPT;
	    if (GetCheck (hDlg, ID_ASAVE)) *modep |= MDASAVE;
	    EndDialog (hDlg, 0);
	    break;
	    
	case 2:		/* Cancel */
	    EndDialog (hDlg, -1);
	    break;
	
	case ID_OVER:
	    /* if OVER set, clear the REP mode check-box */
	    if (GetCheck (hDlg, ID_OVER)) {
		SendMessage (GetDlgItem (hDlg, ID_REP), BM_SETCHECK, 0, 0L);
	    }
	    break;

	case ID_REP:
	    /* if REP set, clear the OVER mode check-box */
	    if (GetCheck (hDlg, ID_REP)) {
		SendMessage (GetDlgItem (hDlg, ID_OVER), BM_SETCHECK, 0, 0L);
	    }
	    break;

	}
	break;

    default:
	return FALSE;
    }
    return FALSE;
} /* ModeDlgProc */

/* IsMenuSeparator:  TRUE if the item is a separator */
/* ===============                                   */
static BOOL PASCAL IsMenuSeparator (HMENU hMenu, int Position)
{
    DWORD state;
    
    if (GetSubMenu (hMenu, Position) != 0) return FALSE;
    if ((state = GetMenuState (hMenu, Position, MF_BYPOSITION)) != -1) {
        if (state & MF_SEPARATOR) return TRUE;
    }
    return FALSE;
} /* IsMenuSeparator */

/* GetMenuEntryID:   give back ID of MenuItem or popup */
/* ==============                                      */

/* if the entry is a menu item (not a popup), its ID is returned. If the
   entry is a popup, the ID of the first entry in this popup is
   returned, added to n*IDM_POPUP where n is the number of popup levels.

   If the entry is neither an item nor a popup, an item id of 0 is
   returned */

UINT  PASCAL    GetMenuEntryID (HMENU hMenu, int Position)
{
    UINT    id;
    HMENU   hSubMenu;

    if (IsMenuSeparator (hMenu, Position)) {
        id = 0;
    }
    else if ((hSubMenu = GetSubMenu (hMenu, Position)) != 0) {
	id = IDM_POPUP + GetMenuEntryID (hSubMenu, 0);
    }
    else id = GetMenuItemID (hMenu, Position);
    return id;
} /* GetMenuEntryID */

/* FindKeyBinding:  scan the key binding table, return first match */
/* ==============                                                  */

/* If there is no match, the returned value point to the BINDNUL entry
   */

KEYTAB * FAR PASCAL FindKeyBinding (void *Func)
{
    register KEYTAB *KTp;

    for (KTp = &keytab[0]; KTp->k_type != BINDNUL; ++KTp) {
	if (((KTp->k_type == BINDFNC) && (KTp->k_ptr.fp == Func)) ||
	    ((KTp->k_type == BINDBUF) && (KTp->k_ptr.buf == Func)))
	    if (!(KTp->k_code & MOUS)) break;   /* found it! (we skip
						   mouse bindings) */
    }
    return KTp;
    
} /* FindKeyBinding */

/* GetKeyText:  translates a key code into a CUA-type description */
/* ==========                                                     */

/* Returns the length (excluding the terminating NULL) of the text if it
   fits in the supplied buffer, 0 otherwise (in which case the buffer
   contents are unuseable). */

/* This function uses the ctrlx_key and meta_key static variables to
   avoid scanning the key binding table too often. If these are NULL,
   however, it looks them up. Therefore, these variables can be zeroed
   if there is a doubt about their validity */

int  PASCAL GetKeyText (int Key, char *Text, int TextLength)
{
    int     i;      /* index in Text */
    char    c;

    if (Key & (CTLX | META)) {
        unsigned *prefix_key_ptr;
        int     n;

        prefix_key_ptr = (Key & CTLX) ? &ctrlx_key : &meta_key;
	if (*prefix_key_ptr == 0) {
	    KEYTAB  *KTp;

	    KTp = FindKeyBinding ((Key & CTLX) ? cex : meta);
	    if (KTp->k_type == BINDNUL) return 0;
	    *prefix_key_ptr = KTp->k_code;
	}
#define NBSPACES    3   /* # of spaces after prefix */
	i = GetKeyText (*prefix_key_ptr, Text, TextLength - NBSPACES);
	if (i == 0)  return 0;
	for (n = 0; n < NBSPACES; ++n) Text[i++] = ' ';
    }
    else {
	i = 0;
    }

#define APPENDTEXT(s)   {if (TextLength - i < sizeof(s)) return 0;\
                         strcpy (&Text[i], s); i += sizeof(s) - 1;}

    c = (char)Key;
    
    if (Key & ALTD) APPENDTEXT(TEXT310) /* "Alt+" */
    
    if (Key & SHFT) APPENDTEXT(TEXT311) /* "Shift+" */
    
    if (Key & CTRL) {
        switch (c) {
	case 'H':
	    APPENDTEXT(TEXT312) /* "BkSp" */
	    goto all_done;
	case 'I':
	    APPENDTEXT(TEXT313) /* "Tab" */
	    goto all_done;
	case 'M':
	    APPENDTEXT(TEXT314) /* "Enter" */
	    goto all_done;
	case '[':
	    APPENDTEXT(TEXT315)  /* "Esc" */
	    goto all_done;
	default:
	    APPENDTEXT(TEXT316) /* "Ctrl+" */
	    break;
	}
    }
    
    if (Key & SPEC) {
        switch (c) {
	case '<':
	    APPENDTEXT(TEXT317) /* "Home" */
	    break;
	case 'N':
	    APPENDTEXT(TEXT318) /* "DownArrow" */
	    break;
	case 'P':
	    APPENDTEXT(TEXT319) /* "UpArrow" */
	    break;
	case 'B':
	    APPENDTEXT(TEXT320) /* "LeftArrow" */
	    break;
	case 'F':
	    APPENDTEXT(TEXT321) /* "RightArrow" */
	    break;
	case '>':
	    APPENDTEXT(TEXT322) /* "End" */
	    break;
	case 'Z':
	    APPENDTEXT(TEXT323) /* "PageUp" */
	    break;
	case 'V':
	    APPENDTEXT(TEXT324) /* "PageDown" */
	    break;
	case 'C':
	    APPENDTEXT(TEXT325) /* "Ins" */
	    break;
	case 'D':	    
	    APPENDTEXT(TEXT326) /* "Del" */
	    break;
	case '0':
	    APPENDTEXT(TEXT327) /* "F10" */
	    break;
	default:    /* single digit function key */
	    if ((c < '1') || (c > '9')) return 0; /* unknown! */
	    if (TextLength - i < 3) return 0;
	    Text[i++] = CHAR328;    /* 'F' */
	    Text[i++] = c;
	    break;
        }
    }
    else if (c == ' ') APPENDTEXT(TEXT329)  /* "SpaceBar" */
    else {
        /* normal char */
        if (TextLength - i < 2) return 0;
        Text[i++] = c;
    }
all_done:    
    Text[i] = '\0';
    return i;
} /* GetKeyText */

/* UpdateMenuItemText:  sets the key binding info in a menu item */
/* ==================                                            */

void  PASCAL    UpdateMenuItemText (HMENU hMenu, int Position,
                                    MENUTAB *MTp)
{
    KEYTAB  *KTp;
    char    NewText[MAXMENUTITLE];
    char    OldText[MAXMENUTITLE];
    register int i;

    GetMenuString (hMenu, Position, OldText, MAXMENUTITLE, MF_BYPOSITION);
    strcpy (NewText, OldText);
    for (i = 0; (NewText[i] != '\0') && (NewText[i] != '\t'); ++i) ;
        /* find the first tab char or the string's end */

    KTp = FindKeyBinding (MTp->m_word & MB_BUF ? (void*)MTp->m_ptr.buf :
						 (void*)MTp->m_ptr.fp);
    if (KTp->k_type != BINDNUL) {
	NewText[i] = '\t';
	if (!GetKeyText (KTp->k_code, &NewText[i+1], MAXMENUTITLE + 1 - i))
	    goto no_binding;    /* if out of room, no binding text */
    }
    else {
no_binding:
        /* let's erase the binding text */
	NewText[i] = '\0';
    }
    if (strcmp (NewText, OldText) == 0) return;
    ModifyMenu (hMenu, Position, MF_BYPOSITION | MF_STRING,
                GetMenuItemID (hMenu, Position), NewText); 
} /* UpdateMenuItemText */

/* InitMenuPopup:   perform popup menu init before display (WM_INITMENUPOPUP) */
/* =============                                                              */

/* here, we may gray menu entries that are invalid. We also try to
   display a key binding after each menu item */

void FAR PASCAL InitMenuPopup (HMENU hMenu, LONG lParam)
{
    int     Position;
    int     ItemCount;
    BOOL    Enable;
    UINT    EntryID;
    register MENUTAB *MTp; /* points the appropriate entry in MenuStaticBind */
    MENUTAB *PrevMTp;      /* to control MenuStaticBind scanning */

    if (HIWORD(lParam)) return;     /* do not process the system menu */

    ctrlx_key = meta_key = 0;       /* these may have changed since the
				       last call to GetKeyText */
    
    /*-Scan the menu's items */
    ItemCount = GetMenuItemCount (hMenu);
    PrevMTp = &MenuStaticBind[0];
    for (Position =  0; Position < ItemCount; Position++) {
        EntryID = GetMenuEntryID (hMenu, Position);

        if (EntryID == 0) continue; /* skip separators */

        /*-lookup the item in the menu binding table */
        /* since we assume the items' order follows the binding table,
	   we try to optimize the lookup by starting from the last
	   found entry */
        if (EntryID >= IDM_POPUP) {
	    /* for popup menus MTp yields the nullproc entry */
            MTp = &MenuStaticBind[0];
        }
        else if (EntryID >= IDM_DYNAMIC) {
	    /* for dynamic items, MTp is directly set to the proper
	       entry in the MenuDynBind */
	    MTp = &MenuDynBind[EntryID - IDM_DYNAMIC];
	}
        else {
            MTp = PrevMTp + 1;
	    for (;;) {
	        if (MTp->m_word == EntryID) {
	            /* found it! */
		    PrevMTp = MTp;  /* prepare for next item */
		    break;
	        }
	        if (MTp == PrevMTp) {
	            /* scan completed, item not found! */
	            MTp = &MenuStaticBind[0]; /* use default */
	            break;
	        }
	        if (MTp->m_word == 0) {
                    MTp = &MenuStaticBind[0];   /* cycle through */
                }
                else ++MTp;
	    }
	}

 	/*-for each item found in the binding table, display a key binding */
	if (MTp != &MenuStaticBind[0]) UpdateMenuItemText (hMenu, Position, MTp);

	/*-if not quiescent, gray most items */
        if (notquiescent) {
            switch (EntryID) {
#ifdef  IDM_DEBUG
	    case IDM_DEBUG:
#endif
	    case IDM_QUIT:
	    case IDM_CTRLG:
	    case IDM_ABOUT:
	    case IDM_WHELPINDEX:
	    case IDM_WHELPKEYBOARD:
	    case IDM_WHELPCOMMANDS:
	    case IDM_WHELPPROCEDURES:
		Enable = TRUE;
		break;
	    default:
		Enable = FALSE;
		break;
	    }
	}
	else {
	    /*-if quiescent, gray the invalid items, depending on
	       appropriate conditions */
	    Enable = TRUE;
            switch (EntryID) {
	    case IDM_FILESAVE:
	    case IDM_UNMARK:
		/* grayed if current buffer is unmarked */
		if (!(curbp->b_flag & BFCHG)) Enable = FALSE;
		break;
            case IDM_NEXTBUFFER:
	    case IDM_USEBUFFER:
	    case IDM_KILLBUFFER:
		/* grayed if sole visible buffer */
		if (getdefb () == NULL) Enable = FALSE;
		break;
	    case IDM_NARROW:
	        /* grayed if already narrowed */
		if (curbp->b_flag & BFNAROW) Enable = FALSE;
	        /* and if no mark... */
            case IDM_CUTREGION:
            case IDM_CLIPREGION:
            case IDM_EDIT_REGION_POPUP:
		/* grayed if no mark0 in current window */
		if (curwp->w_markp[0] == NULL) Enable = FALSE;
		break;
            case IDM_REMMARK:
            case IDM_SWAPMARK:
            case IDM_GOTOMARK:
                /* grayed if no mark at all in current window */
                {
                    int     i;
                    Enable = FALSE;
                    for (i = 0; i <= 9; i++) if (curwp->w_markp[i]) {
                        Enable = TRUE;
                        break;
                    }
                }
                break;
	    case IDM_WIDEN:
		/* grayed if not narrowed */
		if (!(curbp->b_flag & BFNAROW)) Enable = FALSE;
		break;
	    case IDM_DELWIND:
	    case IDM_ONLYWIND:
	    case IDM_NEXTWIND:
	    case IDM_PREVWIND:
	    case IDM_NEXTUP:
	    case IDM_NEXTDOWN:
	    case IDM_FILE_WINDOW_SIZE_POPUP:
		/* grayed if only one window in current screen */
		if (wheadp->w_wndp == NULL) Enable = FALSE;
		break;
	    case IDM_EDIT_CLIPBOARD_POPUP:
		/* grayed if clipboard empty and no mark set */
		if ((curwp->w_markp[0] == NULL) &&
		    !IsClipboardFormatAvailable (CF_TEXT)) Enable = FALSE;
		break;
	    case IDM_INSERTCLIP:
		/* grayed if clipboard empty */
		if (!IsClipboardFormatAvailable (CF_TEXT)) Enable = FALSE;
		break;
	    case IDM_YANK:
		/* grayed if no kill buffer */
		if (kbufh[kill_index] == NULL) Enable = FALSE;
		break;
	    case IDM_FORWHUNT:
	    case IDM_BACKHUNT:
		/* grayed if no search string */
		if (pat[0] == '\0') Enable = FALSE;
		break;
	    case IDM_CTLXE:
		/* grayed if no keyboard macro */
		break;
	    case IDM_TILE_VERTICALLY:
	        if (!Win31API) Enable = FALSE;
	        break;
	    }

	    /*-If view mode, gray the items flagged IDM_NOTVIEW */
	    if (curbp->b_mode & MDVIEW) {
	        if (!(EntryID & (IDM_DYNAMIC | IDM_POPUP)) &&
                    (MTp->m_word & IDM_NOTVIEW)) Enable = FALSE;
	    }
        }

        EnableMenuItem (hMenu, Position,
                        MF_BYPOSITION | (Enable ? MF_ENABLED : MF_GRAYED));
    }
} /* InitMenuPopup */

/* SimulateExtendedKey:    feed an extended key into the input stream */
/* ===================                                                */

static void PASCAL  SimulateExtendedKey (int ec)
{
    char    prefix;

    if (in_room(5)) {
	prefix = ec >> 8;
	if (prefix != 0) {
	    in_put (0);
	    in_put (prefix);
	    if (prefix & (MOUS >> 8)) {
		/* in case the key is a mouse action, supply
		   dummy mouse position info */
		in_put (1);
		in_put (1);
	    }
	}
	in_put (ec & 0xFF);
    }
} /* SimulateExtendedKey */

/* MenuCommand: WM_COMMAND message handling */
/* ===========                              */

/* returns TRUE if the command has been recognized and FALSE otherwise
   */

BOOL FAR PASCAL MenuCommand (UINT wParam, LONG lParam)
{
    FARPROC     ProcInstance;
    DWORD       HelpContext;

    switch (LOWORD(wParam)) {
        /* the menu choices from here down to the default statement are
           valid even in the not-quiescent case */

    case IDM_ABOUT:
	ProcInstance = MakeProcInstance ((FARPROC)AboutDlgProc,
					 hEmacsInstance);
	DialogBox (hEmacsInstance, "ABOUT", hFrameWnd, ProcInstance);
	FreeProcInstance (ProcInstance);
	break;
	
#ifdef  IDM_DEBUG
    case IDM_DEBUG:     /* places a call to the debugger */
	DebugBreak ();
	break;
#endif
	
    case IDM_QUIT:
	PostMessage (hFrameWnd, WM_CLOSE, 0, 0L);
	break;
	
    case IDM_CTRLG:
	/* this is a special case: we feed the abort key into the input
	   stream */
	SimulateExtendedKey (abortc);
	break;
	
    case IDM_WHELPINDEX:
	HelpContext = 0;
	goto InvokeHelp;
    case IDM_WHELPKEYBOARD:
	HelpContext = HELPID_KEYBOARD;
	goto InvokeHelp;
    case IDM_WHELPCOMMANDS:
	HelpContext = HELPID_COMMANDS;
	goto InvokeHelp;
    case IDM_WHELPPROCEDURES:
	HelpContext = HELPID_PROCEDURES;
InvokeHelp:
	WinHelp (hFrameWnd, MainHelpFile,
                 HelpContext ? HELP_CONTEXT : HELP_INDEX, HelpContext);
	MainHelpUsed = TRUE;
	break;
	
    default:
	if (notquiescent) return TRUE;  /* abort processing */

	/* the following IDs are processed only if emacs is quiescent */
	switch (LOWORD(wParam)) {

	case IDM_GLOBMODE:
	case IDM_MODE:
	    ProcInstance = MakeProcInstance ((FARPROC)ModeDlgProc,
					     hEmacsInstance);
	    DialogBoxParam (hEmacsInstance, "MODES",
			    hFrameWnd, ProcInstance,
			    (DWORD)(wParam == IDM_GLOBMODE));
	    FreeProcInstance (ProcInstance);
	    if (wParam = IDM_MODE) {
		upmode ();
		update (FALSE);
	    }
	    break;

	case IDM_FONT:
	    PickEmacsFont ();
	    break;

	case IDM_CASCADE:
	    SendMessage (hMDIClientWnd, WM_MDICASCADE, 0, 0L);
	    break;
	case IDM_TILE_HORIZONTALLY:
	    SendMessage (hMDIClientWnd, WM_MDITILE, MDITILE_VERTICAL, 0L);
	    break;
	case IDM_TILE_VERTICALLY:
	    SendMessage (hMDIClientWnd, WM_MDITILE, MDITILE_HORIZONTAL, 0L);
	    break;
	case IDM_ARRANGEICONS:
	    SendMessage (hMDIClientWnd, WM_MDIICONARRANGE, 0, 0L);
	    break;

	case IDM_NORMALIZE:
	    {
		SCREEN  *sp;

		sp = (SCREEN*)GetWindowLong ((HWND)(SendMessage (hMDIClientWnd,
					            WM_MDIGETACTIVE, 0, 0L)),
		                             GWL_SCRPTR);
		newsize (TRUE, sp->s_nrow);
		newwidth (TRUE, sp->s_ncol);
		update (FALSE);
	    }
	    break;
	    
	default:
	    if (wParam >= IDM_FIRSTCHILD) return FALSE;
	    GenerateMenuSeq (wParam);
	    return TRUE;
	}
    }
    
    /* we have processed an internal menu command */
    if (!notquiescent) GenerateMenuSeq (IDM_NULLPROC);
        /* this flushes a possible numeric argument */
    return TRUE;
} /* MenuCommand */

/* GetScreenMenuHandle: returns the handle to the 'Screen' menu (for MDI mgt) */
/* ===================                                                        */

HMENU FAR PASCAL GetScreenMenuHandle (void)
{
    HMENU   hBaseMenu;
    int     Pos;

    hBaseMenu = GetMenu (hFrameWnd);
    for (Pos = GetMenuItemCount (hBaseMenu) - 1; Pos >= 0; Pos--) {
        if (GetMenuEntryID (hBaseMenu, Pos) == IDM_SCREEN_POPUP) {
            return GetSubMenu (hBaseMenu, Pos);
        }
    }
    return NULL;
} /* GetScreenMenuHandle */

/* MenuEntryCount:  count of menu entries, excluding the MDI buttons */
/* ==============                                                    */

static int PASCAL   MenuEntryCount (HMENU hMenu)
{
    int     Count;

    Count = GetMenuItemCount (hMenu);
    if (hMenu == GetMenu (hFrameWnd)) {
#if WINDOW_MSWIN32
        if (GetWindowLong((HWND)SendMessage (hMDIClientWnd, WM_MDIGETACTIVE,
                                             0, 0L), GWL_STYLE) &
            WS_MAXIMIZE) {
#else
        if (HIWORD(SendMessage (hMDIClientWnd, WM_MDIGETACTIVE, 0, 0L))) {
#endif
            /* an MDI child is maximized ==> we have MDI buttons in the
	       menu bar */
	    Count -= 2;
	}
    }
    return Count;
} /* MenuEntryCount */

/* MenuEntryOffset:  position of the first non-MDI button menu entry */
/* ===============                                                   */

static int PASCAL   MenuEntryOffset (HMENU hMenu)
{
    if (hMenu == GetMenu (hFrameWnd)) {
#if WINDOW_MSWIN32
        if (GetWindowLong((HWND)SendMessage (hMDIClientWnd, WM_MDIGETACTIVE,
                                             0, 0L), GWL_STYLE) &
            WS_MAXIMIZE) {
#else
        if (HIWORD(SendMessage (hMDIClientWnd, WM_MDIGETACTIVE, 0, 0L))) {
#endif
            /* an MDI child is maximized ==> we have MDI buttons in the
	       menu bar */
	    return 1;
	}
    }
    return 0;
    
} /* MenuEntryOffset */

/* ParseMenu:   parse a piece of menu path */
/* =========                               */

static BOOL PASCAL  ParseMenu (char *Name, char *Title, int *Posp)

/* Puts the text part of the menu name in Title (at most MAXMENUTITLE
   characters including the \0) and the position number in *Posp. If no
   position is specified, *Posp is set to -1. The returned BOOL is TRUE
   if no error occured and FAILD if a syntax problem was diagnosed */
{
    register int i;

    *Posp = -1;
    for (i = 0; Name[i] != '\0'; i++) {
        if (i >= MAXMENUTITLE - 1) {
            mlwrite (TEXT300);  /* "[Incorrect menu]" */
            return FAILD;
        }
        if (Name[i] == '>') break;
        if (Name[i] == '@') {   /* must parse a position index */
            unsigned char *s;

            *Posp = 0;
            for (s = &Name[i+1]; (*s != '>') && (*s != '\0'); s++) {
                if ((*s >= '0') && (*s <= '9')) {
                    *Posp = (*Posp * 10) + (*s - '0');
                }
                else {      /* non numerical character! */
                    mlwrite (TEXT300);  /* "[Incorrect menu]" */
                    return FAILD;
                }
            }
            break;
        }
        Title[i] = Name[i];
    }
    Title[i] = '\0';
    return TRUE;
} /* ParseMenu */

/* LocateMenu:  locate a menu entry matching a piece of menu path */
/* ==========                                                     */

static BOOL PASCAL  LocateMenu (char *Name, CURMENU *CM)

/* The returned BOOL is TRUE if a matching menu entry was found, FALSE
   if no such entry was found and FAILD if a syntax problem was
   diagnosed. The CURMENU structure is updated if the entry was found.
   */
{
    HMENU   hMenu;
    int     ItemCount;
    int     Pos, StartPos, PosOffset;
    BOOL    Result, DoScan;
    char    Target[MAXMENUTITLE];
    char    EntryName[MAXMENUTITLE];

    while (*Name == '<') {
	++Name;
	--CM->cm_x;
	CM->cm_pos = -1;
    }
    if ((Result = ParseMenu (Name, Target, &Pos)) != TRUE) {
	return Result;
    }
    if (CM->cm_x < 0) {
	hMenu = GetMenu (hFrameWnd);
    }
    else hMenu = CM->cm_parent[CM->cm_x];
    PosOffset = MenuEntryOffset (hMenu);
    ItemCount = GetMenuItemCount (hMenu);
    if (DoScan = (Pos < 0)) Pos = CM->cm_pos;
    if (Pos < 0) Pos = 0;
    Pos += PosOffset;
    StartPos = Pos;
    do {
	char    *s;

	if (IsMenuSeparator (hMenu, Pos)) {
	    EntryName[0] = '\0';
	}
	else {
            GetMenuString (hMenu, Pos, (LPSTR)&EntryName[0],
		           MAXMENUTITLE, MF_BYPOSITION);
	    if ((s = strchr (EntryName, '\t')) != NULL) *s = '\0';
	        /* discard the accelerator/key-binding info */
	}
	if (strcmp (EntryName, Target) == 0) goto MenuLocated;
        if (!DoScan) {
            if (*Target == '\0') goto MenuLocated;
            else break; /* no matching target at indicated position */
        }
	if (++Pos >= ItemCount) Pos = 0;
    } while (Pos != StartPos);
    return FALSE;   /* not found */
MenuLocated:
    CM->cm_pos = Pos - PosOffset;
    return TRUE;
} /* LocateMenu */

/* AddMenuEntry:    add a menu entry (or, recursively, a cascade of entries) */
/* ============                                                              */

static BOOL PASCAL  AddMenuEntry (char *Name, UINT ID, CURMENU *CM,
                                      WORD *MenuType)
/* the MenuType is a set of flags set returned by this function:
   MT_DUMMY indicates that the menu item is a mere separator, MT_MENUBAR
   indicates that the menu bar has been modified */
{
    HMENU   hMenu, hPopup;
    int     Pos;
    BOOL    Result;
    char    EntryName[MAXMENUTITLE];
    WORD    MenuFlags;

    if ((Result = ParseMenu (Name, EntryName, &Pos)) != TRUE) {
        return Result;
    }
    if (CM->cm_x < 0) {
	hMenu = GetMenu (hFrameWnd);
	*MenuType |= MT_MENUBAR;
    }
    else hMenu = CM->cm_parent[CM->cm_x];
    Name = strchr(Name, '>');
    if (Name != NULL) {     /* our task is to set up a popup menu */
	if (CM->cm_x >= MAXMENUPARENT - 1) {
	    mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
	    return FALSE;
	}
	if ((hPopup = CreatePopupMenu ()) == NULL) {
	    mlwrite (TEXT302);  /* "[lack of resources]" */
	    return ABORT;
	}
	MenuFlags = MF_POPUP | MF_STRING;
    }
    else {  /* this is a menu entry, not a popup */
	if (strcmp (EntryName, "-") == 0) {    /* only a separator */
	    MenuFlags = MF_SEPARATOR;
	    *MenuType |= MT_DUMMY;
	    ID = 0;     /* contrary to SDK documentation, InsertMenu
			   does not ignore the ID for a separator in 3.0! */
	}
	else {
	    MenuFlags = MF_STRING;
	}
    }
    if (Pos < 0) {
        Pos = CM->cm_pos + 1;       /* to insert after current entry */
	if (Pos <= 0) Pos = MenuEntryCount (hMenu); /* to append at end */
    }
    Result = InsertMenu (hMenu, Pos + MenuEntryOffset (hMenu),
                         MenuFlags | MF_BYPOSITION,
                         Name ? (UINT)hPopup : ID,
                         (LPSTR)&EntryName[0]);
    if (!Result) {
        mlwrite (TEXT302);  /* "[lack of resources]" */
        Result = FALSE;
    }
    else Result = TRUE;
    if (Name == NULL) {
        CM->cm_pos = Pos;
    }
    else {
        if (Result != TRUE) {
            DestroyMenu (hPopup);
        }
        else {
	    CM->cm_parent[++CM->cm_x] = hPopup;
	    CM->cm_pos = -1;
	    ++Name;     /* skip the '>' */
	    if ((Result = AddMenuEntry (Name, ID, CM, MenuType)) != TRUE) {
		/* Menu creation FAILD at some point, we must undo our own */
		DeleteMenu (hMenu, Pos + MenuEntryOffset (hMenu),
                            MF_BYPOSITION);
	    }
	}
    }
    return Result;
} /* AddMenuEntry */

/* AddMenuBinding:  bind a menu item to an EPOINTER */
/* ==============                                   */

static BOOL PASCAL  AddMenuBinding (ETYPE EPOINTER eptr, WORD type)

/* called by bindtomenu and macrotomenu, once the function or macro name
   has been parsed */
{
    BOOL    Result;
    WORD    MenuType = 0;   /* info returned by AddMenuEntry */
    char    FullName[NSTRING];
    char    *Name;
    UINT    DynIndex;
    CURMENU CM;

    /*-Let's make sure we have a free entry in the binding table */
    for (DynIndex = 0; MenuDynBind[DynIndex].m_word != 0; DynIndex++) {
        if (DynIndex >= MAXDYNMENU) {
            mlwrite (TEXT17);
            /* "Binding table FULL!" */
            return FALSE;
        }
    }

    /*-Parse the menu Name */
    Name = &FullName[0];
    if ((Result = mlreply (TEXT306, Name, NSTRING)) != TRUE) {
            /* "Menu: " */
        return Result;
    }
    if (*Name == '>') {
        CM.cm_pos = -1;
        CM.cm_x = -1;
        ++Name;
    }
    else CM = CurrentMenu;
    while ((Result = LocateMenu (Name, &CM)) == TRUE) {
        /* ...looping until we hit a piece of the menu path that does
	   not yet exist */
	HMENU   hMenu;
	
	if (CM.cm_x < 0) hMenu = GetMenu (hFrameWnd);
	else hMenu = CM.cm_parent[CM.cm_x];
	hMenu = GetSubMenu (hMenu, CM.cm_pos + MenuEntryOffset (hMenu));
	if (hMenu == NULL) {
	    /* this is not a popup menu, we loose! */
	    mlwrite (TEXT300);  /* "[Incorrect menu]" */
	    return FALSE;
	}
	if (CM.cm_x >= MAXMENUPARENT - 1) {
	    mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
	    return FALSE;
	}
	CM.cm_parent[++CM.cm_x] = hMenu;
	CM.cm_pos = -1;
	Name = strchr (Name, '>');
	if (Name == NULL) {
	    /* premature end of menu path! */
	    mlwrite (TEXT300);  /* "[Incorrect menu]" */
	    return FALSE;
	}
	++Name;     /* skip the '>' */
    }
    if (Result != FALSE) return Result;  /* syntax error, no cigar! */

    /*-Create the menu entry (or cascade of entries) */
    if ((Result = AddMenuEntry (Name, DynIndex + IDM_DYNAMIC, &CM,
                                &MenuType)) != TRUE) {
        return Result;
    }
    CurrentMenu = CM;

    if (!(MenuType & MT_DUMMY)) {
	/*-complete the binding */
	MenuDynBind[DynIndex].m_word = type;
	MenuDynBind[DynIndex].m_ptr = eptr;
    }
    
    if (MenuType & MT_MENUBAR) DrawMenuBar (hFrameWnd);
    return TRUE;
} /* AddMenuBinding */

/* bindtomenu:  bind a menu item to an emacs function */
/* ==========                                         */

PASCAL bindtomenu (int f, int n)
/* command arguments IGNORED */
{
    ETYPE EPOINTER  e;

    e.fp = getname (TEXT304);   /* "Function: " */
    if (e.fp == NULL) {
        mlwrite (TEXT16);       /* "[No such function]" */
        return FALSE;
    }
    return AddMenuBinding (e, MB_FNC);
} /* bindtomenu */

/* macrotomenu: bind a menu item to a macro (i.e. a buffer) */
/* ===========                                              */

PASCAL macrotomenu (int f, int n)
/* command arguments IGNORED */
{
    ETYPE EPOINTER  e;
    char    Name[NBUFN];
    BOOL    Result;

    if ((Result = mlreply(TEXT305, &Name[1], NBUFN-2)) != TRUE) {
            /* "Macro: " */
        return Result;
    }
    Name[0] = '[';
    strcat (Name, "]");
    if ((e.buf = bfind (Name, FALSE, 0)) == NULL) {
        mlwrite (TEXT130);
        /* "Macro not defined" */
        return FALSE;
    }
    return AddMenuBinding (e, MB_BUF);
} /* macrotomenu */

/* DeleteMenuBinding:   remove a menu entry and its binding or sub-entries */
/* =================                                                       */

static BOOL PASCAL  DeleteMenuBinding (HMENU hMenu, int Pos)
/* returns TRUE except when an attempt is made to delete the 'Screen'
   menu */
{
    BOOL    Result;
    UINT    ID;
    HMENU   hPopup;

    Pos += MenuEntryOffset (hMenu);
    if (!IsMenuSeparator (hMenu, Pos)) {
        if ((ID = GetMenuItemID (hMenu, Pos)) != -1) {
            /* this is a pure menu item */
            if (ID >= IDM_DYNAMIC) {
                /* let's free the dynamic binding table entry */
                MenuDynBind[ID - IDM_DYNAMIC].m_word = 0;
            }
        }
        else if ((hPopup = GetSubMenu (hMenu, Pos)) != NULL) {
            /* this is a popup, we must delete all the contained entries */
            int     i;

            if (ID == IDM_SCREEN_POPUP) {
                /* this menu is used by the system's MDI manager to list the
	           MDI childs. We cannot remove it */
                return FALSE;
            }
            for (i = MenuEntryCount (hPopup); i > 0;) {
                DeleteMenuBinding (hPopup, --i);
            }
        }
    }
    DeleteMenu (hMenu, Pos, MF_BYPOSITION);
    if (hMenu == GetMenu (hFrameWnd)) DrawMenuBar (hFrameWnd);
    return TRUE;
} /* DeleteMenuBinding */

/* unbindmenu:  remove a menu entry */
/* ==========                       */

PASCAL unbindmenu (int f, int n)
/* command arguments IGNORED */
{
    BOOL    Result;
    char    FullName[NSTRING];
    char    *Name;
    CURMENU CM;

    Name = &FullName[0];
    if ((Result = mlreply (TEXT303, Name, NSTRING)) != TRUE) {
            /* "Menu: " */
        return Result;
    }
    if (*Name == '>') {
        CM.cm_pos = -1;
        CM.cm_x = -1;
        ++Name;
    }
    else CM = CurrentMenu;
    while ((Result = LocateMenu (Name, &CM)) == TRUE) {
	HMENU   hMenu;
	
	if (CM.cm_x < 0) hMenu = GetMenu (hFrameWnd);
	else hMenu = CM.cm_parent[CM.cm_x];
	Name = strchr (Name, '>');
	if (Name != NULL) {     /* the menu path continues beyond this... */
	    hMenu = GetSubMenu (hMenu, CM.cm_pos + MenuEntryOffset (hMenu));
	    if (hMenu == NULL) {
	        /* this is not a popup menu, we loose! */
	        Result = FALSE;
	        break;
	    }
	    if (CM.cm_x >= MAXMENUPARENT - 1) {
		mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
		return FALSE;
	    }
	    CM.cm_parent[++CM.cm_x] = hMenu;
	    CM.cm_pos = 0;
	    ++Name;     /* skip the '>' */
	    /* and loop once again... */
	}
	else {                  /* this is it: the end of the menu path */
	    int     Pos;

	    if ((Result = DeleteMenuBinding (hMenu, CM.cm_pos)) != TRUE) {
	        return FALSE;
	    }
	    if ((Pos = MenuEntryCount (hMenu) - 1) < CM.cm_pos) {
		/* we just deleted the item at the menu's end. This
		   position is no longer valid */
		CM.cm_pos = Pos;
	    }
	    CurrentMenu = CM;
	    break;      /* we are done */
	}
    }
    /* we arrive here if the menu was not found or a syntax error occured */
    if (Result == FALSE) mlwrite (TEXT300); /* "[Incorrect menu]" */
    return Result;
} /* unbindmenu */
